package com.yugao.fintech.libra.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yugao.fintech.libra.auth.api.AuthApi;
import com.yugao.fintech.libra.common.constants.OAuth2Cons;
import com.yugao.fintech.libra.common.constants.TenantCons;
import com.yugao.fintech.libra.common.enums.RoleEnum;
import com.yugao.fintech.libra.common.module.auth.AuthUserTenant;
import com.yugao.fintech.libra.common.module.auth.LoginUser;
import com.yugao.fintech.libra.common.module.auth.UserAuthority;
import com.yugao.fintech.libra.common.module.auth.UserDataRule;
import com.yugao.fintech.libra.common.util.SecurityUtils;
import com.yugao.fintech.libra.common.tenant.core.utils.TenantUtils;
import com.yugao.fintech.libra.model.entity.*;
import com.yugao.fintech.libra.model.vo.tenant.TenantResp;
import com.yugao.fintech.libra.model.vo.tenant.TenantUserResp;
import com.yugao.fintech.libra.model.vo.user.*;
import com.yugao.fintech.libra.stat.api.StatisticSender;
import com.yugao.fintech.libra.stat.api.constant.StatisticCons;
import com.yugao.fintech.libra.stat.api.model.mq.StatisticMQ;
import com.yugao.fintech.libra.system.assembly.SysDeptAssembly;
import com.yugao.fintech.libra.system.assembly.SysRoleAssembly;
import com.yugao.fintech.libra.system.assembly.SysTenantAssembly;
import com.yugao.fintech.libra.system.assembly.SysUserAssembly;
import com.yugao.fintech.libra.system.config.TenantProperties;
import com.yugao.fintech.libra.system.manager.SysRoleManager;
import com.yugao.fintech.libra.system.manager.SysTenantManager;
import com.yugao.fintech.libra.system.manager.SysTenantUserManager;
import com.yugao.fintech.libra.system.manager.SysUserManager;
import com.yugao.fintech.libra.system.mapper.SysTenantUserMapper;
import com.yugao.fintech.libra.system.mapper.SysUserMapper;
import com.yugao.fintech.libra.system.mapper.SysUserRoleMapper;
import com.yugao.fintech.libra.system.model.entity.*;
import com.yugao.fintech.libra.model.vo.dept.DeptResp;
import com.yugao.fintech.libra.system.model.rqrs.tenant.*;
import com.yugao.fintech.libra.system.service.*;
import com.yugao.fintech.meheco.core.exception.BusinessException;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service
public class TenantUserServiceImpl extends ServiceImpl<SysTenantUserMapper, SysTenantUser> implements TenantUserService {
    @Autowired
    private SysUserMapper userMapper;
    @Autowired
    private StatisticSender statisticSender;
    @Autowired
    private SysUserRoleMapper userRoleMapper;
    @Autowired
    private SysRoleManager roleManager;
    @Autowired
    private SysUserAssembly userAssembly;
    @Autowired
    private SysUserManager userManager;
    @Autowired
    private SysDeptAssembly deptAssembly;
    @Autowired
    private AuthorizeService authorizeService;
    @Autowired
    private SysRoleAssembly roleAssembly;
    @Autowired
    private SysTenantUserManager tenantUserManager;
    @Autowired
    private SysTenantManager tenantManager;
    @Autowired
    private TenantProperties tenantProperties;
    @Autowired
    private SysUserService userService;
    @Autowired
    private TenantService tenantService;
    @Autowired
    private SysRoleService roleService;
    @Autowired
    private SysDeptService deptService;
    @Autowired
    private SysMenuDataRuleService menuDataRuleService;
    @Autowired
    private RamResourceService ramResourceService;
    @Autowired
    private SysMenuService menuService;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private SysTenantAssembly tenantAssembly;
    @Autowired
    private AuthApi authApi;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public IPage<TenantUserResp> pageAllTenantUser(UserBasePageReq req) {
        // 只有平台用户才可以查看
        Long tenantId = TenantContextHolder.get().getTenantId();
        if (TenantCons.SYSTEM_TENANT_ID.compareTo(tenantId) != 0) {
            throw new BusinessException("当前功能只有平台用户才能查看");
        }
        IPage<SysTenantUser> pageData = TenantUtils.executeIgnore(() -> userMapper.pageTenantUser(PageUtil.to(req), null, req));

        List<Long> tenantIds = pageData.getRecords().stream().map(SysTenantUser::getTenantId).collect(Collectors.toList());
        if (CollectionUtil.isEmpty(tenantIds)) {
            return new Page<>();
        }

        Map<Long, SysTenant> tenantMap = tenantManager.listByIds(tenantIds).stream()
                .collect(Collectors.toMap(SysTenant::getId, Function.identity()));

        // 查询这些租户的所有角色
        Map<String, String> ownerMap = TenantUtils.executeIgnore(() -> {
            List<SysUserRole> userRoleList = userRoleMapper.selectList(SysUserRole.lqw().in(SysUserRole::getTenantId, tenantIds));

            // 租户拥有者处理, key: tenantId + userId value: 恒为 owner
            return userRoleList.stream().filter(e -> RoleEnum.OWNER.getCode().equals(e.getRoleCode()))
                    .collect(Collectors.toMap(e -> e.getTenantId() + "" + e.getUserId(), SysUserRole::getRoleCode, (k1, k2) -> k1));
        });


        return PageUtil.to(pageData, e -> {
            TenantUserResp resp = userAssembly.toTenantUserResp(e.getUser());
            resp.setTenantId(e.getTenantId());
            resp.setTenantName(Optional.ofNullable(tenantMap.get(e.getTenantId())).map(SysTenant::getName).orElse(""));
            resp.setAddTime(e.getCreatedAt());
            resp.setIsOwner(ownerMap.containsKey(e.getTenantId() + "" + e.getUserId()));
            resp.setTenantUrl(tenantProperties.getUrl(e.getTenantId()));
            return resp;
        });
    }

    @Override
    public IPage<UserResp> pageUser(UserBasePageReq req) {
        Long tenantId = TenantContextHolder.get().getTenantId();

        IPage<SysTenantUser> pageData = userMapper.pageTenantUser(PageUtil.to(req), tenantId, req);
        List<Long> userIds = pageData.getRecords().stream().map(SysTenantUser::getUserId).collect(Collectors.toList());
        Map<Long, List<SysRole>> roleMap = roleManager.listRoleByUserIds(userIds);
        return PageUtil.to(pageData, e -> {
            UserResp resp = userAssembly.toUserResp(e.getUser());
            List<SysRole> roleList = roleMap.getOrDefault(e.getUserId(), Collections.emptyList());
            resp.setRoleList(roleList.stream().map(roleAssembly::toRoleResp).collect(Collectors.toList()));
            return resp;
        });
    }

    @Override
    public Map<Long, String> listDeptNamesByUserIds(List<Long> userIds) {
        if (CollectionUtil.isEmpty(userIds)) {
            log.debug("userIds is empty");
            return new HashMap<>();
        }
        List<UserDeptResp> list = this.userMapper.listDeptNamesByUserIds(userIds);
        Map<Long, String> res = new HashMap<>();
        list.forEach(item -> res.merge(item.getUserId(), item.getDeptName(), (a, b) -> a + "," + b));
        return res;
    }

    @Override
    public UserResp getUserById(Long userId) {
        SysTenantUser tenantUser = tenantUserManager.getOne(SysTenantUser.lqw().eq(SysTenantUser::getUserId, userId));
        if (Objects.isNull(tenantUser)) {
            throw new BusinessException("当前租户不存在该用户");
        }
        return userService.getUserById(userId);
    }

    @Override
    public List<DeptResp> listUserDept(Long userId) {
        List<SysDept> list = userMapper.selectUserDept(userId);
        return list.stream().map(deptAssembly::toDeptTreeResp).collect(Collectors.toList());
    }

    @Override
    @Transactional
    @FieldUniqueCheck(type = OperationTypeEnum.UPDATE)
    public boolean updateById(SysUser user) {
        List<Long> roleIdList = user.getRoleIds();

        List<Long> deptIdList = user.getDeptIds();

        // 检查角色, 排除拥有者角色, 一个租户下只能有一个租户拥有该角色, 如果想变更就单独调用接口转移, 或者平台端转移租户所有者
        roleIdList = roleManager.checkRole(roleIdList).stream()
                .filter(e -> !RoleEnum.OWNER.getCode().equals(e.getRoleCode()))
                .map(SysRole::getId).collect(Collectors.toList());

        // 分配用户角色
        authorizeService.assignUserRole(user.getUserId(), roleIdList);
        // 分配用户部门
        authorizeService.assignUserDept(user.getUserId(), user.getDeptIds());
        return userMapper.updateById(user) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteUserByIds(List<Long> userIds) {
        userManager.checkUserAllowed(userIds);

        authorizeService.deleteUserDept(userIds);

        authorizeService.deleteUserRole(userIds);

        tenantUserManager.deleteByUseIds(userIds);

        // 发出删除用户消息-删除其余数据
//        userIds.forEach(mqManager::sendDeleteUserMsg);
    }

    @Override
    public void checkUserAllowed(Long userId) {
        userManager.checkUserAllowed(userId);
    }

    @Override
    public String importUser(List<UserSaveReq> userList, Boolean isUpdateSupport, String operName) {
        return null;
    }

    @Override
    public List<UserResp> listRoleUser(RoleUserListReq req) {
        List<SysUser> list = userMapper.selectUserInRole(req.getRoleId(), req.getUserName());
        return list.stream().map(userAssembly::toUserResp).collect(Collectors.toList());
    }

    @Override
    // @DataScope(deptId = "#req.deptId")
    public IPage<DeptUserResp> pageDeptUser(DeptUserBasePageReq req) {
        IPage<SysUser> page = userMapper.selectDeptUser(PageUtil.to(req), req);
        List<Long> userIds = page.getRecords().stream().map(SysUser::getUserId).collect(Collectors.toList());

        Map<Long, String> userDeptNames = this.listDeptNamesByUserIds(userIds);

        return PageUtil.to(page, e -> {
            DeptUserResp resp = userAssembly.toDeptUserResp(e);
            resp.setDeptNames(userDeptNames.get(e.getUserId()));
            return resp;
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void assignUser(TenantUserAddReq req) {
        if (CollectionUtil.isEmpty(req.getUserIds())) {
            return;
        }
        Set<Long> existUserIds = this.list(SysTenantUser.lqw().in(SysTenantUser::getUserId, req.getUserIds()))
                .stream().map(SysTenantUser::getUserId).collect(Collectors.toSet());
        Set<Long> reqUserIds = new HashSet<>(req.getUserIds());
        reqUserIds.removeAll(existUserIds);

        List<SysTenantUser> saveTenantUser = reqUserIds.stream().map(userId -> new SysTenantUser().setUserId(userId))
                .collect(Collectors.toList());
        tenantUserManager.saveBatch(saveTenantUser);

        // 判断租户是否有指定的默认角色, 没有就创建
        SysRole role = getDefaultRole();

        // 为租户成员分配默认角色
        reqUserIds.forEach(userId -> authorizeService.assignUserRole(userId, Collections.singletonList(role.getId())));
    }

    /**
     * 即使调用当前方法的下面的步骤执行失败, 也没有影响
     */
    private SysRole getDefaultRole() {
        SysRole defaultRole = roleManager.getByCode(RoleEnum.MEMBER.getCode());
        if (Objects.nonNull(defaultRole)) {
            return defaultRole;
        }
        RoleEnum roleEnum = RoleEnum.MEMBER;
        defaultRole = new SysRole().setRoleCode(roleEnum.getCode()).setRoleName(roleEnum.getName())
                .setRoleSort(roleEnum.getSort());
        roleManager.save(defaultRole);
        return defaultRole;
    }

    @Override
    public List<Long> listUserIdsByRoleId(Long roleId) {
        return userManager.listUserIdsByRoleIds(Collections.singletonList(roleId));
    }

    @Override
    public List<UserResp> listUserByIds(List<Long> userIds) {
        if (CollectionUtil.isEmpty(userIds)) {
            return Collections.emptyList();
        }
        return userManager.list(SysUser.lqw().in(SysUser::getUserId, userIds)).stream().map(userAssembly::toUserResp)
                .collect(Collectors.toList());
    }

    @Override
    public List<TenantResp> listUserTenant() {
        LambdaQueryWrapper<SysTenantUser> tenantUserLqw = SysTenantUser.lqw()
                .eq(SysTenantUser::getUserId, SecurityUtils.getUserId()).orderByDesc(SysTenantUser::getLastLoginTime);

        List<Long> tenantIds = TenantUtils.executeIgnore(() ->
                tenantUserManager.list(tenantUserLqw).stream().map(SysTenantUser::getTenantId).collect(Collectors.toList())
        );

        if (CollectionUtil.isEmpty(tenantIds)) {
            throw new BusinessException("您尚未归属于任何租户");
        }

        LambdaQueryWrapper<SysTenant> tenantLqw = SysTenant.lqw().in(SysTenant::getId, tenantIds)
                .eq(SysTenant::getStatus, Integer.valueOf(TenantCons.Status.NORMAL));

        // 根据 tenantIds 结果顺序进行排序, 第一个是最近登录的
        Map<Long, SysTenant> tenantMap = tenantManager.list(tenantLqw).stream().collect(Collectors.toMap(SysTenant::getId, Function.identity()));
        List<SysTenant> tenants = tenantIds.stream().filter(tenantMap::containsKey).map(tenantMap::get).collect(Collectors.toList());
        return tenants.stream().map(tenantAssembly::toTenantResp).collect(Collectors.toList());
    }

    @Override
    public List<TenantResp> listUserTenantTree() {
        Map<Long, TenantResp> respMap = new HashMap<>();
        List<TenantResp> userTenants = this.listUserTenant();
        Map<Long, TenantResp> tenantMap = userTenants.stream().collect(Collectors.toMap(TenantResp::getId, Function.identity()));
        for (TenantResp tenant : userTenants) {
            if (AuthUserTenant.mainTenant(tenant.getMainTenantId(), tenant.getId())) {
                respMap.putIfAbsent(tenant.getId(), tenant);
            } else {
                TenantResp mainTenant = tenantMap.get(tenant.getMainTenantId());
                TenantResp main = respMap.computeIfAbsent(mainTenant.getId(), (k) -> mainTenant);
                List<TenantResp> children = Optional.ofNullable(main.getChildren()).orElse(new ArrayList<>());
                children.add(tenant);
                main.setChildren(children);
            }
        }

        return new ArrayList<>(respMap.values());
    }

    @Override
    public SwitchTenantResp switchTenant(SwitchTenantReq req) {
        // 这里从缓存中获取最新的数据(要新的并且全的, 如果从请求头取然后更新缓存, 可能会导致数据丢失, 因为网关并没有将所有用户数据传递到下游)
        LoginUser loginUser = authApi.getLoginUser();
        // 判断是否租户越权
        boolean exist = tenantManager.list().stream().map(SysTenant::getId).anyMatch(e -> e.compareTo(req.getTenantId()) == 0);
        if (!exist) {
            throw new BusinessException("租户越权");
        }

        SysTenant tenant = tenantService.validTenant(req.getTenantId());
        List<SysTenantUser> tenantUserList = tenantUserManager.listByUserId(loginUser.getUserId());
        Set<String> owningTenantIds = tenantUserList.stream().map(SysTenantUser::getTenantId).map(String::valueOf)
                .collect(Collectors.toSet());

        return TenantUtils.execute(TenantCons.Type.ALL, req.getTenantId(), () -> {
            Long userId = loginUser.getUserId();
            SysUser user = userManager.getById(userId);

            // 查询用户角色
            List<SysRole> roleList = roleService.listRoleByUserId(loginUser.getUserId());
            List<Long> roleIds = roleList.stream().map(SysRole::getId).collect(Collectors.toList());

            List<String> roleCodes = roleList.stream().map(SysRole::getRoleCode).collect(Collectors.toList());
            boolean isOwner = RoleEnum.isTenantOwner(roleCodes);

            // 菜单权限集合
            List<SysMenu> menuList = menuService.listAllMenuByRoleIds(isOwner, roleIds, tenant.getSceneId());

            Set<String> allPermissions = isOwner ? Collections.singleton("*:*:*") : authorizeService.listActionByUserId(userId);

            // API资源
            List<RamApiResource> apiResourceList = ramResourceService.listResource(isOwner, allPermissions);

            // 数据规则集合
            List<Long> permissionIds = menuList.stream().map(SysMenu::getId).collect(Collectors.toList());
            List<SysMenuRule> menuRules = new ArrayList<>();

            if (!isOwner) {
                menuRules = menuDataRuleService.listPermissionRule(roleIds, permissionIds);
            }

            // 查询用户所在部门
            List<DeptResp> deptList = deptService.listUserDept(new UserDeptListReq(userId));

            // 按照服务编号分组权限资源, 有利于网关统一鉴权的时候, 快速定位到具体某个服务下的某个模块, 缩小查询的权限数, 避免用户所拥有权限数据过大
            // 导致查询缓存中间件效率低下问题
            Map<String, List<RamApiResource>> resourceMap = apiResourceList.stream()
                    .filter(e -> Objects.nonNull(e.getModuleCode()))
                    .collect(Collectors.groupingBy(RamApiResource::getModuleCode));
            Map<String, UserAuthority> authorityMap = new HashMap<>();

            Map<Long, SysMenuRule> dataRuleMap = menuRules.stream().collect(Collectors.toMap(SysMenuRule::getMenuId, Function.identity()));
            Map<String, List<SysMenu>> menuMap = menuList.stream().collect(Collectors.groupingBy(SysMenu::getPerms));

            // 获取权限数据, 包括action(操作), 资源url, 数据规则等
            resourceMap.forEach((k, v) -> {
                Set<String> resourceUrls = v.stream().map(RamApiResource::getUrl).collect(Collectors.toSet());
                Set<String> actions = v.stream().map(RamApiResource::getAction).collect(Collectors.toSet());
                List<UserDataRule> dataRuleList = v.stream()
                        .filter(e -> menuMap.containsKey(e.getAction()))
                        .flatMap(e ->
                                menuMap.get(e.getAction()).stream()
                                        .filter(e1 -> dataRuleMap.containsKey(e1.getId()))
                                        .map(e1 -> dataRuleMap.get(e1.getId()))
                        ).map(userAssembly::toUserDataRule).collect(Collectors.toList());

                UserAuthority userAuthority = new UserAuthority()
                        .setDataRules(dataRuleList).setActions(actions).setResourceUrls(resourceUrls);
                authorityMap.put(k, userAuthority);
            });

            AuthUserTenant authUserTenant = new AuthUserTenant().setTenantId(req.getTenantId())
                    .setMainTenantId(tenant.getMainTenantId()).setIsOwner(isOwner)
                    .setDeptIds(deptList.stream().map(DeptResp::getId).collect(Collectors.toSet()))
                    .setRoleCodes(roleCodes);

            // 保存用户权限信息
            String userPermKey = String.format(OAuth2Cons.CACHE_USER_AUTHORITY, loginUser.getUserId(), req.getTenantId());
            redisTemplate.opsForHash().putAll(userPermKey, authorityMap);

            // 更新当前用户当前租户信息
            loginUser.setTenant(authUserTenant);
            loginUser.setOwningTenantIds(owningTenantIds);
            authApi.updateLoginUser(loginUser);

            SwitchTenantResp resp = new SwitchTenantResp();
            resp.setUserName(loginUser.getUserName()).setRealName(user.getRealName()).setUserId(userId);
            return resp;
        });
    }

    @Override
    public SwitchTenantResp selectLoginTenant(SelectLoginTenantReq req) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        // 选择工作空间(租户)
        SwitchTenantReq switchTenantReq = new SwitchTenantReq();
        switchTenantReq.setTenantId(req.getTenantId());
        SwitchTenantResp resp = this.switchTenant(switchTenantReq);
        TenantUtils.execute(req.getTenantId(), () -> {
            // 处理登录日志
            LoginLogMQ.MsgPayload loginLog = buildLoginLog(loginUser);
            loginLog.setMessage("登录成功");
            loginLog.setCostTime(0L);
            rabbitTemplate.convertAndSend(LoginLogMQ.EXCHANGE, LoginLogMQ.KEY, LoginLogMQ.build(loginLog).toMessage());

            JSONObject statistic = new JSONObject();
            statistic.put("isLogin", true);
            statisticSender.send(StatisticMQ.build(StatisticCons.Type.LOGIN_LOGOUT, statistic));
        });

        // 更新最后登录时间
        TenantUtils.execute(req.getTenantId(), () -> tenantUserManager.updateLastLoginTime(loginUser.getUserId()));
        return resp;
    }

    private LoginLogMQ.MsgPayload buildLoginLog(LoginUser loginUser) {
        LoginLogMQ.MsgPayload payload = new LoginLogMQ.MsgPayload();
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequestOfNonNull().getHeader("User-Agent"));
        // 获取客户端操作系统
        String os = userAgent.getOperatingSystem().getName();
        // 获取客户端浏览器
        String browser = userAgent.getBrowser().getName();

        payload.setBrowser(browser);
        payload.setOs(os);
        payload.setIp(IpUtils.getRequestIp(new HttpRequestServletManager(ServletUtils.getRequestOfNonNull())));
        payload.setTenantId(TenantContextHolder.get().getTenantId());
        payload.setUserName(loginUser.getUserName());
        payload.setClientId(loginUser.getClientId());
        return payload;
    }

    @Override
    public void initTenant(Long tenantId) {
        LoginUser loginUser = authApi.getLoginUser();
        // 通过租户id, 获取到主租户id, 并设置到用户信息中
        if (Objects.isNull(tenantId) || Objects.isNull(loginUser)) {
            return;
        }
        SysTenant tenant = tenantManager.getById(tenantId);
        if (Objects.isNull(tenant)) {
            log.error("tenant id [{}] not exist", tenantId);
            return;
        }
        Long mainTenantId = tenant.getMainTenantId();

        // 获取所有子租户
        Set<String> owningTenantIds = tenantManager.listSubTenant(mainTenantId).stream()
                .map(e -> String.valueOf(e.getId()))
                .collect(Collectors.toSet());
        owningTenantIds.add(String.valueOf(mainTenantId));

        AuthUserTenant authUserTenant = new AuthUserTenant().setTenantId(tenant.getId())
                .setMainTenantId(mainTenantId).setIsOwner(false)
                .setDeptIds(Collections.emptySet())
                .setRoleCodes(Collections.emptyList());

        // 更新当前用户当前租户信息
        loginUser.setTenant(authUserTenant);
        loginUser.setOwningTenantIds(owningTenantIds);
        authApi.updateLoginUser(loginUser);
    }
}
